package com.tpay.server.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.unitils.core.TestListener;
import org.unitils.core.Unitils;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;

public class GuiceUnitilsJUnit4ClassRunner extends BlockJUnit4ClassRunner {
	private static final Logger logger = LoggerFactory.getLogger(GuiceUnitilsJUnit4ClassRunner.class);
	private final Injector injector;
	
	@Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface MyGuiceModules {
    	Class<?>[] value();
    }
	
	public GuiceUnitilsJUnit4ClassRunner(Class<?> klass) throws InitializationError {
		super(klass);
		
		Class<?>[] classes = getModulesFor(klass);
		injector = createInjectorFor(classes);
	}
	
	private Class<?>[] getModulesFor(Class<?> klass) throws InitializationError {
        MyGuiceModules annotation = klass.getAnnotation(MyGuiceModules.class);
        if(annotation == null)
                throw new InitializationError("Missing @GuiceModules annotation for unit test '" + klass.getName() + "'");

        return annotation.value();
	}
	
	private Injector createInjectorFor(Class<?>[] classes) throws InitializationError {
    	Module[] modules = new Module[classes.length];
    	for(int i = 0; i < classes.length; i++) {
    		try {
    			modules[i] = (Module)(classes[i]).newInstance();
    		} catch(InstantiationException e) {
    			throw new InitializationError(e);
    		} catch(IllegalAccessException e) {
    			throw new InitializationError(e);
    		}
    	}
    	return Guice.createInjector(modules);
    }

	@Override
	protected Object createTest() throws Exception {
		Object obj = super.createTest();
		injector.injectMembers(obj);
		return obj;
	}

	/**** 여기서부터는 Unitils 관련 ****/
	
	/**
	 * @return unitils test listener
	 */
	private TestListener getTestListener() {
		return getUnitils().getTestListener();
	}
	
	/**
	 * unitils의 디폴트 싱글톤 인스턴스 리턴
	 * 
	 * @return unitils instance, not null
	 */
	private Unitils getUnitils() {
		return Unitils.getInstance();
	}
	
	@Override
	protected Statement withBeforeClasses(Statement statement) {
		Statement junitStatement = super.withBeforeClasses(statement);
		return new RunBeforeClass(junitStatement, getTestClass().getJavaClass(), getTestListener());
	}

	@SuppressWarnings("deprecation")
	@Override
	protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
		Statement junitStatement = super.withBefores(method, target, statement);
		return new RunBefore(junitStatement, target, method.getMethod(), getTestListener());
	}

	@Override
	protected Statement methodInvoker(FrameworkMethod method, Object test) {
		Statement junitStatement = super.methodInvoker(method, test);
		return new MyInvokeMethod(junitStatement, test, method.getMethod(), getTestListener());
	}

	@SuppressWarnings("deprecation")
	@Override
	protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
		Statement junitStatement = super.withAfters(method, target, statement);
		return new RunAfter(junitStatement, target, method.getMethod(), getTestListener());
	}
}
